/*----------------------------------------------------------------------*
 *	CLASS:		Qt3GameEngine
 *	Quantum Tic Tac Toe
 *	Copyright (c) 2004-2009 Novatia Labs, Inc.  All rights reserved.
 *----------------------------------------------------------------------*/

package com.novatialabs.qttt.engine.impl;

import com.novatialabs.qttt.engine.Qt3ClassicalBoard;
import com.novatialabs.qttt.engine.Qt3Game;
import com.novatialabs.qttt.engine.Qt3InvalidMoveException;
import com.novatialabs.qttt.engine.Qt3Player;
import com.novatialabs.qttt.engine.Qt3Square;

/**
 * Qt3GameEngine is an instance of a Quantum Tic Tac Toe game.
 *
 * @Author	John Levine
 * @Author	Duane Gibson
 * @Author Friendly development resources cheerfully provided by Charles Phares.
 * @Author	$Author$
 * @Version	$Revision$
 */

public class Qt3GameEngine
    implements Qt3Game
{


    ////////////////////////////////////////////////////////////////////////

    private static final String revisionString ="Qttt $Revision: 1.58 DRG.E3 $";

    ////////////////////////////////////////////////////////////////////////


    private int			stepsComplete;
    private Qt3Round[]		tableOfRounds;
    private Qt3Step[]		tableOfSteps;

    /*package*/ static final int	MAX_MOVES	= 9;
    private static final int	MAX_STEPS	= (MAX_MOVES * 2);

   /*-------------------------------------------------------------------*
    *	CONSTRUCTOR:	Qt3GameEngine()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Sat Feb 28 12:52:42 PST 2004
    *-------------------------------------------------------------------*/
   /**
    * Constructs a new QT3 game that is awaiting the first move.
    */

    public Qt3GameEngine()
    {

	this.setInitialGameState();

    } // Qt3GameEngine()

   /*-------------------------------------------------------------------*
    *	METHOD:		setInitialGameState()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Sat Feb 28 12:52:42 PST 2004
    *-------------------------------------------------------------------*/
   /**
    * Initialized this object to a new, initial game state.
    */

    private void setInitialGameState()
    {

	this.stepsComplete = 0;
	final Qt3Round[] roundTable = new Qt3Round[MAX_MOVES];
	this.tableOfRounds = roundTable;
	final Qt3Step[]  stepTable = new Qt3Step[MAX_STEPS];
	this.tableOfSteps = stepTable;

	Qt3Step prevStep = null;
	int roundIndex = 0;
	int stepIndex = 0;
	for ( int loopCounter = 1 ; loopCounter <= MAX_MOVES ; ++loopCounter ) {

	    Qt3Round round = new Qt3Round(loopCounter, prevStep);
	    roundTable[roundIndex++] = round;

	    stepTable[stepIndex++] = round.getMove();
	    prevStep = round.getResolution();
	    stepTable[stepIndex++] = prevStep;
	}

    } // setInitialGameState()

   /*-------------------------------------------------------------------*
    *	METHOD:		getNextStep()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan  3 21:35:55 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the next Qt3Step to be filled in.
    * @throws IllegalStateException if all moves have been played (and
    *		the game is over).
    */

    private Qt3Step getNextStep()
    {
	int stepIndex = this.stepsComplete;
	if ( stepIndex >= MAX_STEPS ) {
	    throw new IllegalStateException();
	}
	return this.tableOfSteps[stepIndex];
    } // getNextStep()

   /*-------------------------------------------------------------------*
    *	METHOD:		getStateOfPreviousStep()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan  3 21:38:49 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the state of the game as of the last Qt3Step filled in.
    */

    private Qt3State getStateOfPreviousStep()
    {
	final int nextStepIndex = this.stepsComplete;

	if ( nextStepIndex > 0 ) {
	    return this.tableOfSteps[(nextStepIndex - 1)].getState();
	} else {
	    return null;
	}

    } // getStateOfPreviousStep()

   /*-------------------------------------------------------------------*
    *	METHOD:		isGameOver()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan  3 20:51:30 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns whether the game is over.
    */

    public boolean isGameOver()
    {
	final Qt3State state = this.getStateOfPreviousStep();
	if ( state == null ) {
	    return false;
	}
	return state.isGameOver();
    } // isGameOver()

   /*-------------------------------------------------------------------*
    *	METHOD:		getWinner()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan  3 20:50:56 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the winner of the game.
    * If "X" won, returns the constant 'Qt3Player.X'.
    * If "O" won, returns the constant 'Qt3Player.O'.
    * If the game is a "Cat's Game", returns null.
    * @throws IllegalStateException if the game is not over.
    */

    public Qt3Player getWinner()
	throws IllegalStateException
    {
	final Qt3State state = this.getStateOfPreviousStep();
	if ( state == null ) {
	    throw new IllegalStateException();
	}
	return state.getWinner();
    } // getWinner()

   /*-------------------------------------------------------------------*
    *	METHOD:		getWinnerScore()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Wed Feb  9 11:06:19 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the score of the winner.
    * @throws IllegalStateException if the game is not over.
    */

    public Score getWinnerScore()
    {
	final Qt3State state = this.getStateOfPreviousStep();
	if ( state == null ) {
	    throw new IllegalStateException();
	}
	return state.getWinnerScore();
    } // getWinnerScore()

   /*-------------------------------------------------------------------*
    *	METHOD:		getLoserScore()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Wed Feb  9 11:16:31 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the score of the loser.
    * @throws IllegalStateException if the game is not over.
    */

    public Score getLoserScore()
    {
	final Qt3State state = this.getStateOfPreviousStep();
	if ( state == null ) {
	    throw new IllegalStateException();
	}
	return state.getLoserScore();
    } // getLoserScore()

   /*-------------------------------------------------------------------*
    *	METHOD:		getMoveNumber()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan  3 20:47:10 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the number (1-9) of the next move.
    * @throws IllegalStateException if all moves have been played (and
    *		the game is over).
    */

    public int getMoveNumber()
    {
	return this.getNextStep().getMoveNumber();
    } // getMoveNumber()

   /*-------------------------------------------------------------------*
    *	METHOD:		getPlayerToMove()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan  3 20:01:54 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the player who should make the next move.
    * @throws IllegalStateException if all moves have been played (and
    *		the game is over).
    */

    public Qt3Player getPlayerToMove()
    {
	return this.getNextStep().getPlayer();
    } // getPlayerToMove()

   /*-------------------------------------------------------------------*
    *	METHOD:		getPlayerForMove()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Tue Jan 11 15:25:35 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the player who made (or will make?) the indicated move.
    *
    * @param moveNumber	number of the move (1-9). 
    */

    /*package*/ static Qt3Player getPlayerForMove( int move )
    {

	if ( (move % 2) == 1 ) {
	    return Qt3Player.X;
	}

	return Qt3Player.O;

    } // getPlayerForMove()

   /*-------------------------------------------------------------------*
    *	METHOD:		isResolutionNeeded()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan  3 19:57:07 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns whether the next step is a resolution.
    */

    public boolean isResolutionNeeded()
    {
	return ((this.stepsComplete % 2) == 1);
    } // isResolutionNeeded()

   /*-------------------------------------------------------------------*
    *	METHOD:		getBoardSquares()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Tue Feb 15 19:08:04 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns a description of the squares in the quantum board.
    */

    public Qt3Square[] getBoardSquares()
    {
	Qt3State state = this.getStateOfPreviousStep();
	if ( state == null ) {
	    state = new Qt3State();
	}
	return state.getBoardSquares();
    } // getBoardSquares()

   /*-------------------------------------------------------------------*
    *	METHOD:		getClassicalEnsemble()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan  3 19:54:28 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the set of classical Tic Tac Toe games currently represented
    * by the state of the Quantum Tic Tac Toe game.
    */

    public Qt3ClassicalBoard[] getClassicalEnsemble()
    {
	final Qt3State state = this.getStateOfPreviousStep();
	if ( state == null ) {
	    return null;
	}
	return state.getClassicalEnsemble();
    } // getClassicalEnsemble()

   /*-------------------------------------------------------------------*
    *	METHOD:		getNotationForMoves()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Wed Feb  9 21:08:24 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the standard notation for all the moves of the game.
    */

    public String[] getNotationForMoves()
    {
	String[] notes = new String[MAX_MOVES];

	final Qt3Round[] roundTable = this.tableOfRounds;

	for ( int roundIndex = 0 ; roundIndex < MAX_MOVES ; ++roundIndex ) {
	    final Qt3Round round = roundTable[roundIndex];

	    StringBuffer buf = new StringBuffer();
	    round.getMove().appendAsString(buf, round.getResolution());
	    notes[roundIndex] = buf.toString();

	}

	return notes;

    } // getNotationForMoves()

   /*-------------------------------------------------------------------*
    *	METHOD:		getNotationForMove()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan  3 19:13:27 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the standard notation for the specified move of the game.
    *
    * @param	move	The move for which the notation is requested.
    * @throws	IllegalArgumentException if the move is not a valid move
    *			number (less than one or greater than nine).
    */

    public String getNotationForMove(
	int	move
	)
	throws IllegalArgumentException
    {

	// validate that move is in range 1..9 inclusive
	if ( (move < 1) || (move > 9) ) {
	    throw new IllegalArgumentException(
		    "Invalid move '" + move + "'");
	}

	final int roundIndex = move - 1;

	final Qt3Round round = this.tableOfRounds[roundIndex];
	StringBuffer buf = new StringBuffer();
	round.getMove().appendAsString(buf, round.getResolution());

	return buf.toString();

    } // getNotationForMove()

   /*-------------------------------------------------------------------*
    *	METHOD:		getClassicalNotationForMoves()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan  3 16:27:21 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the classical notation for all the moves of the game.
    */

    public String[] getClassicalNotationForMoves()
    {
	String[] notes = new String[MAX_MOVES];

	final Qt3Round[] roundTable = this.tableOfRounds;
	final Qt3State	prevState	= this.getStateOfPreviousStep();
	final Qt3Square[] boardSquares = ((prevState == null)
				? null
			       	: prevState.getBoardSquares());
	final int movesSoFar = ((this.stepsComplete + 1) / 2);

	for ( int roundIndex = 0 ; roundIndex < MAX_MOVES ; ++roundIndex ) {
	    if ( boardSquares == null ) {
		notes[roundIndex] = "";
	    } else if ( roundIndex >= movesSoFar ) {
		notes[roundIndex] = "";
	    } else {
		notes[roundIndex] =
			roundTable[roundIndex].getMove().getClassicalNotation(
			    boardSquares);
	    }
	}

	return notes;

    } // getClassicalNotationForMoves()


   /*-------------------------------------------------------------------*
    *	METHOD:		placeMove()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Sat Feb 28 13:34:51 PST 2004
    *-------------------------------------------------------------------*/
   /**
    * Places the next move of the game in the indicated squares.
    *
    * @param	square1		number of the first square in which to move.
    * @param	square2		number of the second square in which to move.
    * @return whether this move ends the game.
    * @throws	IllegalArgumentException if either square is an invalid square
    *			number (less than one or greater than nine).
    * @throws	Qt3InvalidMoveException	if the arguments do not identify
    *			 a valid move.
    * @throws	IllegalStateException if a move is not the next step in the
    *			 game or the game is over.
    */

    public boolean placeMove(
	int	square1,
	int	square2
	)
	throws	IllegalArgumentException,
		Qt3InvalidMoveException,
		IllegalStateException
    {

	final Qt3Step nextStep = this.getNextStep();

	// report error if game state does not expect this input
	if ( ! (nextStep instanceof Qt3Move) ) {
	    throw new IllegalStateException();
	}

	final Qt3Move thisMove = (Qt3Move)nextStep;

	// DRG TODO: handle special situations on the 9th move where only one square may be selected.

	if ( (square1 < 1) || (square1 > 9) ) {
	    throw new IllegalArgumentException(
		    "Invalid square1 '" + square1 + "'");
	}

	if ( (square2 < 1) || (square2 > 9) ) {
	    throw new IllegalArgumentException(
		    "Invalid square2 '" + square2 + "'");
	}

	int		square1Index	= square1 - 1;	// index is 0-based
	int		square2Index	= square2 - 1;	// index is 0-based

	// if move matches next redo, do not flush future moves.
	if ( thisMove.isRedoable() ) {
	    // check to see if this is identical to the move in the
	    // history.
	    if (  (    (thisMove.getFirstSquareIndex() == square1Index)
		    && (thisMove.getSecondSquareIndex() == square2Index) )
		|| (   (thisMove.getFirstSquareIndex() == square2Index)
		    && (thisMove.getSecondSquareIndex() == square1Index)) ) {
		// turn this into a "redo" command (to preserve the history)
		return redoMove();
	    }
	}

	thisMove.clear();
	try {
	    // DRG TODO: report error if square1 no longer accepts moves
	    thisMove.setFirstSquareIndex(square1Index);
	    // DRG TODO: report error if square2 no longer accepts moves
	    thisMove.setSecondSquareIndex(square2Index);
	} catch ( RuntimeException rtEx ) {
	    thisMove.clear();	// avoid leaving part of the move applied
	    throw rtEx;
	}

	return this.progressGame(thisMove);

    } // placeMove()

   /*-------------------------------------------------------------------*
    *	METHOD:		resolveSquareToMove()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Sat Jun  5 16:17:44 PDT 2004
    *-------------------------------------------------------------------*/
   /**
    * Resolves an entanglement created by the current move.
    *
    * @param	square		The square chosen for the resolution.
    * @param	move		The move that was chosen to exist in the square.
    * @return whether this resolution ends the game.
    * @throws	IllegalArgumentException if the square is an invalid square
    *			number (less than one or greater than nine) or the
    *			move does not identify a valid move number.
    * @throws	Qt3InvalidMoveException	if the arguments do not resolve the
    *			current entanglement.
    * @throws	IllegalStateException if a resolution is not the next step
    *			in the game or the game is over.
    */

    public boolean resolveSquareToMove(
	int	square,
	int	move
	)
	throws	IllegalArgumentException,
		Qt3InvalidMoveException,
		IllegalStateException
    {

	final Qt3Step nextStep = this.getNextStep();

	// report error if game state does not expect this input
	if ( ! (nextStep instanceof Qt3Resolution) ) {
	    throw new IllegalStateException();
	}
	final Qt3Resolution resolution = (Qt3Resolution)nextStep;

	if ( (square < 1) || (square > 9) ) {
	    throw new IllegalArgumentException(
		    "Invalid square '" + square + "'");
	}

	if ( (move < 1) || (move > this.stepsComplete) ) {
	    throw new IllegalArgumentException(
		    "Invalid move '" + move + "'");
	}

	// DRG TODO: if move matches next redo, do not flush future moves.

	// DRG TODO: report error if move is not in the indicated square
	if ( false ) {
	    throw new IllegalArgumentException(
		    "Move '" + move + "' is not in square '" + square + "'");
	}

	// DRG TODO: report error if move is not part of the cycle to be resolved
	if ( false ) {
	    throw new Qt3InvalidMoveException(
		    move // .. DRG TODO: make info in exception descriptive
		    );
	}

	final int	squareIndex = square - 1;	// index is 0-based

	resolution.setResolution(
		resolution.normalizeResolution(squareIndex, move));

	return this.progressGame(resolution);

    } // resolveSquareToMove()

   /*-------------------------------------------------------------------*
    *	METHOD:		progressGame()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan  3 23:02:21 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Progresses the game to the point where it is over or the next
    * player input is required.
    *
    * @return	whether game is over.
    */

    private boolean progressGame(
	Qt3Step	currentStep
	)
    {

	++this.stepsComplete;	// We just completed a step.

	this.clearFutureMoves();

	currentStep.evaluateState();

	// For a move:
	// DRG TODO: deal with cycle resolution based on new move
	// DRG TODO: deal with updating fancy state for new move
	// DRG TODO: including whether game is over
	// For a resolution:
	//   all in the "resolution" constructor in Qt3State

	if ( currentStep.getState().isGameOver() ) {
	    // Note: Only a resolution or the 9th move into a single
	    // empty square (which implies its own collapse) can finalize
	    // a game.
	    return true;
	}

	//
	// Now progress through any steps that are automatic.
	//

	int newStepCount = this.stepsComplete;
	Qt3Step newStep;

	while ( (newStepCount < MAX_STEPS)
		&& (newStep = this.tableOfSteps[newStepCount]).isAutomatic() ) {
	    newStep.evaluateState();
	    ++newStepCount;
	}

	this.stepsComplete = newStepCount;

	// DRG TODO: update any other "view" information to correspond

	return this.isGameOver();

    } // progressGame()

   /*-------------------------------------------------------------------*
    *	METHOD:		clearFutureMoves()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Sat Jun 19 17:09:00 PDT 2004
    *-------------------------------------------------------------------*/
   /**
    * Clears any remaining "redo-able" moves (because we just added a new
    * move).
    */

    private void clearFutureMoves()
    {

	int stepToCheck = this.stepsComplete;
	while ( stepToCheck < MAX_STEPS ) {
	    final Qt3Step	nextStep = this.tableOfSteps[stepToCheck];

	    if ( ! nextStep.isRedoable() ) {
		break;
	    }

	    nextStep.clear();
	}

    } // clearFutureMoves()


   /*-------------------------------------------------------------------*
    *	METHOD:		newGame()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Sat Feb 28 12:55:18 PST 2004
    *-------------------------------------------------------------------*/
   /**
    * Resets the game state to that of a new, initial game.
    */

    public void newGame()
    {

	this.setInitialGameState();

    } // newGame()

   /*-------------------------------------------------------------------*
    *	METHOD:		undoMove()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Sat Feb 28 13:20:49 PST 2004
    *-------------------------------------------------------------------*/
   /**
    * Changes the state of the game back to the state before the previous
    * move or entablement-resolution.  The un-done moves are remembered and
    * can be re-applied with redoMove().
    *
    * @throws	IllegalStateException if there are no moves to undo.
    */

    public void undoMove()
    {

	if ( this.stepsComplete < 1 ) {
	    throw new IllegalStateException("There are no moves to undo.");
	}

	int newStepCount = this.stepsComplete;
	--newStepCount;
	while ( (newStepCount > 0) // We know the first step is not automatic.
		&& this.tableOfSteps[newStepCount].isAutomatic() ) {
	    --newStepCount;
	}

	this.stepsComplete = newStepCount;
	// DRG TODO: update any other "view" information to correspond

    } // undoMove()

   /*-------------------------------------------------------------------*
    *	METHOD:		redoMove()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Sat Feb 28 13:20:49 PST 2004
    *-------------------------------------------------------------------*/
   /**
    * Re-applies a move that was previously un-done with undoMove() or
    * resetMoves().
    *
    * @return whether this move ends the game.
    * @throws	IllegalStateException if there are no moves to re-apply.
    */

    public boolean redoMove()
    {

	// DRG TODO: handle attempts to "redo" past the end-of-game condition

	int newStepCount = this.stepsComplete;

	// handle attempts to "redo" a move that does not yet exist.
	if ( ! this.tableOfSteps[newStepCount].isRedoable() ) {
	    throw new IllegalStateException(
		    "There are no more moves to re-apply.");
	}

	while ( (newStepCount < MAX_STEPS)
		&& this.tableOfSteps[newStepCount].isAutomatic() ) {
	    ++newStepCount;
	}

	this.stepsComplete = newStepCount;

	// DRG TODO: update any other "view" information to correspond

	return this.isGameOver();

    } // redoMove

   /*-------------------------------------------------------------------*
    *	METHOD:		resetMoves()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Sat Feb 28 13:24:22 PST 2004
    *-------------------------------------------------------------------*/
   /**
    * Performs an undoMove() on all previous moves in the game.
    * The moves can be re-applied with redoMove().
    */

    public void resetMoves()
    {

	if ( this.stepsComplete < 1 ) {
	    return;	// no moves to reset
	}

	this.stepsComplete = 0;
	// DRG TODO: update any other "view" information to correspond

    } // resetMoves()

   /*-------------------------------------------------------------------*
    *	METHOD:		isNormalizingMoveSquares()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Sat Mar  6 14:59:44 PST 2004
    *-------------------------------------------------------------------*/
   /**
    * Returns whether moves should be normalized (to have the lowest square
    * index first).
    */

    public boolean isNormalizingMoveSquares() {
	// FUTURE: make this a configurable option on the game
	return true;
    } // isNormalizingMoveSquares()

   /*-------------------------------------------------------------------*
    *	METHOD:		getVersionString()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Sat Feb 28 12:55:18 PST 2004
    *-------------------------------------------------------------------*/
   /**
    * Returns a revision string identifying the version of this
    * implementation of Quantum Tic Tac Toe.
    */

    public String getVersionString()
    {
	return Qt3GameEngine.revisionString;
    } // getVersionString()


} // class Qt3GameEngine

